/* map */
//array.map(function(currentValue,index,arr),thisArgs)

/* 
    map遍历数组每一个元素，每一个元素都会去执行function(currentValue,index,arr)；
    thisArgs:传入function(currentValue,index,arr)作为他的this（箭头函数没有this，无法改变）
    不能map空数组
 */
Array.prototype.myMap = function (fn, thisArgs) {
  // 判断第一个参数是否为函数
  if (typeof fn === "function") {
    throw Error("参数错误");
  }
  //定义最后返回的数组
  let resArry = [];
  //获取到当前数组，对他每一项进行遍历
  let currentArry = this;
  //判断是否是空数组
  if (currentArry.length === 0) {
    throw Error("不能为空数组");
  }
  for (let index = 0; index < currentArry.length; index++) {
    //   把每一项都调用fn函数执行，讲thisArgs传入作为this
    //function(currentValue,index,arr)回调函数
    resArry[index] = fn.call(thisArgs, currentArry[index], index, currentArry);
  }
  return resArry;
};

/* reduce */
//array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
/* 
    reduce遍历数组每一个元素，每一个元素都会去执行function(total, currentValue, currentIndex, arr)；
    initialValue:传入初始值，如果没有传入则默认初始值为第一项且从第二项开始。
    数组和初始值只能有一个为空
 */

Array.prototype.myReduce = function (callBack, initVlaue) {
  //   判断第一个参数是否为函数
  if (typeof callBack !== "function") {
    throw Error("参数错误");
  }
  //   获取当前数组
  let currentArry = this;
  // 空数组不能没有初始值
  if (currentArry.length === 0 && arguments.length === 1) {
    throw Error("Reduce of empty array with no initial value");
  }
  //   判断是否传入参数 决定从哪一位开始遍历
  //   当initValue没有传入时，数组第一位作为初始值，从第二位开始遍历
  let index = arguments.length === 1 ? 1 : 0;
  //   判断是否传入参数 决定初始值
  let res = arguments.length === 1 ? currentArry[0] : initVlaue;
  //   遍历数组，执行回调函数
  for (index; index < currentArry.length; index++) {
    res = callBack(res, currentArry[index], index, currentArry);
  }
  return res;
};
/*  使用reduce实现map */
// 利用reduce遍历每一项，将每一项带入回调函数
Array.prototype._map = function (callBack, thisArgs) {
  // 判断第一个参数是否为函数
  if (typeof callBack !== "function") {
    throw Error("参数错误");
  }
  // 返回的结果
  let resArry = [];
  // 获取当前的数组
  let currentArry = this;
  // 遍历每一项，一定设置默认值，不设置将从第二项开始遍历
  currentArry.reduce((total, currentValue, currentIndex, currentArry) => {
    // 调用fn，讲thisArgs传给fn执行，讲返回结果保存在新数组中
    resArry[currentIndex] = callBack.call(
      thisArgs,
      currentValue,
      currentIndex,
      currentArry
    );
  }, 0);
  return resArry;
};

/* filter */
//array.filter(function(currentValue,index,arr), thisValue)

Array.prototype.myFilter = function (callBack, thisValue) {
  //判断fn是否为函数
  if (typeof callBack !== "function") {
    throw Error("参数错误");
  }
  let currentArry = this;
  //判断该数组是否为空
  if (currentArry.length === 0) {
    throw Error("数组不能为空");
  }
  //获取thisValue,作为回调函数的this指向
  let _this = thisValue ? thisValue : undefined;
  // 定义
  let resArry = [];
  for (let index = 0; index < currentArry.length; index++) {
    if (callBack.call(_this, currentArry[index], index, currentArry)) {
      resArry.push(currentArry[index]);
    }
  }
  return resArry;
};

/* reverse */
//reverse() 方法用于颠倒数组中元素的顺序。
// 新增传参可以选择哪一位开始到哪一位结束 start ：开始位数默认为0；end：结束位数默认为数组最后一位，超过数组长度也为最后一位

Array.prototype.myReverse = function (start, end) {
  //初始化左边的指针 传入start不存在或者小于零 初始都指向第一位
  let left = start === undefined || start < 0 ? 0 : start;
  // 初始化右边指针 传入end不存在或者大于数组长度 初始指向最后一位
  let right =
    end === undefined || end > this.length - 1 ? this.length - 1 : end;
  // 交换顺序
  while (left < end) {
    // 两数交换
    this[left] = this[left] + this[right];
    this[right] = this[left] - this[right];
    this[left] = this[left] - this[right];
    left++; //左指针右移
    right--; //右指针左移
  }
  return this;
};

/* flat */
//Array.prototype.flat() 用于将嵌套的数组“拉平”，变成一维的数组。该方法返回一个新数组，对原数据没有影响。
// 不传参数时，默认“拉平”一层，可以传入一个整数，表示想要“拉平”的层数。
// 传入 <=0 的整数将返回原数组，不“拉平”
// Infinity 关键字作为参数时，无论多少层嵌套，都会转为一维数组
// 如果原数组有空位，Array.prototype.flat() 会跳过空位。

//第一版 用map实现flat(Infinity) 跳过空位（不在map里面返回）
Array.prototype.myFlat1 = function () {
  let resArry = [];
  let currentArry = this;
  //遍历每一项
  currentArry.map((currentValue) => {
    // 如果这一项是数组则继续扁平化
    if (Object.prototype.toString.call(currentValue) === "[object Array]") {
      //将这一项扁平化后和resArry连接
      resArry = resArry.concat(currentValue.myFlat1());
    } else {
      // 该项不是数组直接放到resArry
      resArry = resArry.concat(currentValue);
    }
  });
  return resArry;
};

// 第二版 用reduce实现flat(Infinity) 会跳过空位
Array.prototype.myFlat2 = function () {
  let resArry = [];
  let currentArry = this;
  //reduce 最后结果为一个数组 默认值为[] 通过 [].concat(currentValue)将所有值连接起来
  resArry = currentArry.reduce((preValue, currentValue) => {
    // 判断当前一项是否是数组类型的
    if (Object.prototype.toString.call(currentValue) === "[object Array]") {
      // 递归调用将该项扁平化
      return preValue.concat(currentValue.myFlat2());
    } else return preValue.concat(currentValue);
  }, []);
  return resArry;
};

// 最终版 控制拉平层数
Array.prototype.myFlat3 = function (deepIndex = 1) {
  // 判断出入的拉平层数 默认为1 不是数字或者小于0返回原数组
  if (!Number(deepIndex) || deepIndex <= 0) {
    return this;
  }
  // 不能直接赋值，会影响原数组
  let resArry = [...this];
  // 根据拉平层数
  while (deepIndex) {
    // 判断数组内是否还有数组 没有就强制结束
    if (resArry.some((item) => Array.isArray(item))) {
      // 通过reduce 一层一层拉平数组
      resArry = resArry.reduce((preValue, currentValue) => {
        return preValue.concat(currentValue);
      }, []);
      deepIndex--;
    } else break;
  }
  return resArry;
};

/* 字符串去重 */
// 利用对象键值对的
// 每个元素 ：typeof currentValue(区分数字类型和字符类型) + （区分引用类型）JSON.stringify(currentValue)获得唯一标识
Array.prototype.myUniqueArry = function () {
  let obj = {};
  let currentArry = this;
  let resArry = [];
  // 遍历每一个元素
  currentArry.map((currentValue) => {
    // 获取每一个元素的标识
    let key = typeof currentValue + JSON.stringify(currentValue);
    // 判断该元素是否存在于对象中
    if (!obj[key]) {
      // 不存在就设置，并push到resArry中
      obj[key] = 1;
      resArry.push(currentValue);
    }
  });
  return resArry;
};
